#include <jni.h>

#include <string>
#include <sstream>

#if defined(__arm__)
  #if defined(__ARM_ARCH_7A__)
    #if defined(__ARM_NEON__)
      #if defined(__ARM_PCS_VFP)
        #define ABI "armeabi-v7a/NEON (hard-float)"
      #else
        #define ABI "armeabi-v7a/NEON"
      #endif
    #else
      #if defined(__ARM_PCS_VFP)
        #define ABI "armeabi-v7a (hard-float)"
      #else
        #define ABI "armeabi-v7a"
      #endif
    #endif
  #else
    #define ABI "armeabi"
  #endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64)  /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif

std::string to_stdstring(JNIEnv *env, jstring jstr);
jstring to_jstring(JNIEnv *env, std::string str);

#ifdef __cplusplus
extern "C" {
#endif

// static method

JNIEXPORT jstring JNICALL
Java_cc_eevee_jni_libstatic_Strings_helloFromJNI(JNIEnv *env, jclass clazz) {
  return env->NewStringUTF("Hello from JNI! Compiled with ABI " ABI ".");
}

JNIEXPORT jstring JNICALL
Java_cc_eevee_jni_libstatic_Strings_join(JNIEnv *env, jclass clazz,
    jstring delimiter, jobjectArray elements) {
  jsize elem_end = env->GetArrayLength(elements) - 1;

  const char *delimiter_chars = env->GetStringUTFChars(delimiter, NULL);

  std::ostringstream ss;
  for (jsize i = 0; i < elem_end; i++) {
    jstring elem = (jstring) env->GetObjectArrayElement(elements, i);
    const char *elem_chars = env->GetStringUTFChars(elem, NULL);
    ss << elem_chars << delimiter_chars;
    env->ReleaseStringUTFChars(elem, elem_chars);
  }
  if (elem_end >= 0) {
    jstring elem = (jstring) env->GetObjectArrayElement(elements, elem_end);
    const char *elem_chars = env->GetStringUTFChars(elem, NULL);
    ss << elem_chars;
    env->ReleaseStringUTFChars(elem, elem_chars);
  }

  env->ReleaseStringUTFChars(delimiter, delimiter_chars);
  env->DeleteLocalRef(elements);
  return to_jstring(env, ss.str());;
}

// instance method

JNIEXPORT jstring JNICALL
Java_cc_eevee_jni_libstatic_Strings_hello(JNIEnv *env, jobject thiz, jstring text) {
  std::ostringstream ss;
  ss << "Hello, " << to_stdstring(env, text) << "!";
  return to_jstring(env, ss.str());
}

#ifdef __cplusplus
}
#endif

std::string to_stdstring(JNIEnv *env, jstring jstr) {
  if (!jstr) return "";

  jclass jstr_class = env->GetObjectClass(jstr);
  jmethodID jstr_getBytes = env->GetMethodID(
      jstr_class, "getBytes", "(Ljava/lang/String;)[B");
  jbyteArray jstr_bytes = (jbyteArray) env->CallObjectMethod(
      jstr, jstr_getBytes, env->NewStringUTF("UTF-8"));

  jsize jstr_len = env->GetArrayLength(jstr_bytes);
  jbyte* jbyte_ptr = env->GetByteArrayElements(jstr_bytes, NULL);
  std::string ret = std::string((char *)(jbyte_ptr), jstr_len);
  env->ReleaseByteArrayElements(jstr_bytes, jbyte_ptr, JNI_ABORT);

  env->DeleteLocalRef(jstr_bytes);
  env->DeleteLocalRef(jstr_class);
  return ret;
}

jstring to_jstring(JNIEnv *env, std::string str) {
  if (str.empty()) return env->NewStringUTF("");

  size_t str_len = str.length();

  jbyteArray jbytes = env->NewByteArray(str_len);
  env->SetByteArrayRegion(jbytes, 0, str_len, (jbyte *)(str.c_str()));

  jclass jstr_class = env->FindClass("java/lang/String");
  jmethodID jstr_init  = env->GetMethodID(
      jstr_class, "<init>", "([BLjava/lang/String;)V");
  return (jstring) env->NewObject(
      jstr_class, jstr_init, jbytes, env->NewStringUTF("UTF-8"));
}
